#ifndef _BasicNeuralNetwork_
#define _BasicNeuralNetwork_
#define BinaryIO
#include<cstdio>
#include<cstdlib>
#include<fstream>
#include<cmath>
#include<initializer_list>
#ifndef BinaryIO
#include<iostream>
using namespace std;
#else
using std::initializer_list;
#endif
bool Rate(double x){
    return ((rand()<<15)|rand())<x*(double)0x40000000;
}
double Random(double low,double high){
    return low+(rand()/(double)0x8000)*(high-low);
}
template<class T>
void ReadFile(FILE* File,T& Class){
#ifdef BinaryIO
    uint8_t* Pointer=(uint8_t*)&Class;
    for(register uint32_t i=0;i<sizeof(T);i++)
        Pointer[i]=fgetc(File);
#else
	cin>>Class;
#endif
}
template<class T>
void WriteFile(FILE* File,T& Class){
#ifdef BinaryIO
    uint8_t* Pointer=(uint8_t*)&Class;
    for(register uint32_t i=0;i<sizeof(T);i++)
        fputc(Pointer[i],File);
#else
	cout<<Class<<endl;
#endif
}
template<class T>
void WriteFileImmediately(FILE *File,T Class){
#ifdef BinaryIO
    uint8_t* Pointer=(uint8_t*)&Class;
    for(register uint32_t i=0;i<sizeof(T);i++)
        fputc(Pointer[i],File);
#else
	cout<<Class<<endl;
#endif
}
double Tanh(double x){return (exp(x)-exp(-x))/(exp(x)+exp(-x));}
double Sigmoid(double x){return 1.0/(1.0+exp(-x));}
double ReLU(double x){return x>0.0?x:0.0;}
double Linear(double x){return x;}
enum{
	BasicNeuralNetworkNullFunctionException,
	BasicNeuralNetworkDeleteNodeException,
	BasicNeuralNetworkDeleteLayerException,
	BasicNeuralNetworkAddNodeException
};
class BasicNeuralNetwork{
    private:
        struct NerveUnit{
            uint32_t Count;
            double* Params;
            double Offset;
            NerveUnit(uint32_t cnt){
                Count=cnt;
                Params=(double*)malloc(Count*sizeof(double));
                for(register uint32_t i=0;i<Count;i++)
                    Params[i]=Random(-1.0,1.0);
                Offset=Random(-1.0,1.0);
            }
            NerveUnit(uint32_t cnt,bool flag){
                Count=cnt;
                Params=(double*)malloc(Count*sizeof(double));
                if(flag){
                    for(register uint32_t i=0;i<Count;i++)
                        Params[i]=Random(-1.0,1.0);
                    Offset=Random(-1.0,1.0);
                }
            }
            ~NerveUnit(){
                free(Params);
            }
            void AddMemory(){
                Count++;
                double* NewParams=(double*)malloc(Count*sizeof(double));
                for(register uint32_t i=0;i<Count-1;i++)
                    NewParams[i]=Params[i];
                NewParams[Count-1]=Random(-1.0,1.0);
                free(Params);
                Params=NewParams;
            }
            void DeleteParam(uint32_t Position){
                Count--;
                double* NewParams=(double*)malloc(Count*sizeof(double));
                for(register uint32_t i=0;i<Count;i++)
                    NewParams[i]=Params[i>=Position?i+1:i];
                free(Params);
                Params=NewParams;
            }
        };
    public:
        BasicNeuralNetwork()=default;
        BasicNeuralNetwork(BasicNeuralNetwork&&)=default;
        BasicNeuralNetwork(const BasicNeuralNetwork&)=default;
        BasicNeuralNetwork& operator=(BasicNeuralNetwork&&)=default;
        BasicNeuralNetwork& operator=(const BasicNeuralNetwork&)=default;
        uint32_t LayerCount;
        uint32_t* NodeCount;
        NerveUnit*** Network;
        uint32_t InputCount;
        uint32_t OutputCount;
        double(*Func)(double)=Tanh;
        void ReadFunc(register FILE* File){
            uint32_t sign;
            ReadFile(File,sign);
            if(sign==0)
                Func=Tanh;
            else if(sign==1)
                Func=Sigmoid;
            else if(sign==2)
                Func=ReLU;
            else if(sign==3)
                Func=Linear;
            else{
            	throw BasicNeuralNetworkNullFunctionException;
            	Func=NULL;
			}
            return;
        }
        void ReadFromFile(const char* FileName){
#ifndef BinaryIO
        	freopen(FileName,"r",stdin);
#endif
            register FILE* File=fopen(FileName,"rb");
            ReadFile(File,InputCount);
            ReadFile(File,OutputCount);
            ReadFile(File,LayerCount);
            ReadFunc(File);
            NodeCount=(uint32_t*)malloc(LayerCount*sizeof(uint32_t));
            Network=(NerveUnit***)malloc((LayerCount+1)*sizeof(NerveUnit**));
            for(register uint32_t i=0;i<LayerCount;i++){
                ReadFile(File,NodeCount[i]);
                Network[i]=(NerveUnit**)malloc(NodeCount[i]*sizeof(NerveUnit*));
                for(register uint32_t j=0;j<NodeCount[i];j++){
                    uint32_t Count;
                    ReadFile(File,Count);
                    Network[i][j]=new NerveUnit(Count,false);
                    for(register uint32_t k=0;k<Network[i][j]->Count;k++)
                        ReadFile(File,Network[i][j]->Params[k]);
                    ReadFile(File,Network[i][j]->Offset);
                }
            }
            Network[LayerCount]=(NerveUnit**)malloc(OutputCount*sizeof(NerveUnit*));
            for(register uint32_t i=0;i<OutputCount;i++){
                uint32_t Count;
                ReadFile(File,Count);
                Network[LayerCount][i]=new NerveUnit(Count,false);
                for(register uint32_t j=0;j<Network[LayerCount][i]->Count;j++)
                    ReadFile(File,Network[LayerCount][i]->Params[j]);
                ReadFile(File,Network[LayerCount][i]->Offset);
            }
            fclose(File);
#ifndef BinaryIO
            freopen("CON","r",stdin);
#endif
        }
        void WriteToFile(const char* FileName){
#ifndef BinaryIO
        	freopen(FileName,"w",stdout);
#endif
            register FILE* File=fopen(FileName,"wb");
            WriteFile(File,InputCount);
            WriteFile(File,OutputCount);
            WriteFile(File,LayerCount);
            if(Func==Tanh)
                WriteFileImmediately(File,0);
            else if(Func==Sigmoid)
                WriteFileImmediately(File,1);
            else if(Func==ReLU)
                WriteFileImmediately(File,2);
            else if(Func==Linear)
                WriteFileImmediately(File,3);
            else
                WriteFileImmediately(File,4);
            for(register uint32_t i=0;i<LayerCount;i++){
                WriteFile(File,NodeCount[i]);
                for(register uint32_t j=0;j<NodeCount[i];j++){
                    WriteFile(File,Network[i][j]->Count);
                    for(register uint32_t k=0;k<Network[i][j]->Count;k++)
                        WriteFile(File,Network[i][j]->Params[k]);
                    WriteFile(File,Network[i][j]->Offset);
                }
            }
            for(register uint32_t i=0;i<OutputCount;i++){
                WriteFile(File,Network[LayerCount][i]->Count);
                for(register uint32_t j=0;j<Network[LayerCount][i]->Count;j++)
                    WriteFile(File,Network[LayerCount][i]->Params[j]);
                WriteFile(File,Network[LayerCount][i]->Offset);
            }
            fclose(File);
#ifndef BinaryIO
            freopen("CON","w",stdout);
#endif
        }
        BasicNeuralNetwork(const char* FileName){
            ReadFromFile(FileName);
        }
        BasicNeuralNetwork(uint32_t Input,uint32_t Output,uint32_t Layer,uint32_t* Node){
            this->LayerCount=Layer;
            this->InputCount=Input;
            this->OutputCount=Output;
            this->NodeCount=(uint32_t*)malloc(this->LayerCount*sizeof(uint32_t));
            this->Network=(NerveUnit***)malloc((this->LayerCount+1)*sizeof(NerveUnit**));
            for(register uint32_t i=0;i<this->LayerCount;i++){
                this->NodeCount[i]=Node[i];
                this->Network[i]=(NerveUnit**)malloc(this->NodeCount[i]*sizeof(NerveUnit*));
                for(register uint32_t j=0;j<this->NodeCount[i];j++)
                    this->Network[i][j]=new NerveUnit(i==0?this->InputCount:this->NodeCount[i-1]);
            }
            this->Network[LayerCount]=(NerveUnit**)malloc(this->OutputCount*sizeof(NerveUnit*));
            for(register uint32_t i=0;i<this->OutputCount;i++)
                this->Network[this->LayerCount][i]=new NerveUnit(this->NodeCount[this->LayerCount-1]);
        }
        BasicNeuralNetwork(uint32_t Input,uint32_t Output,uint32_t Layer,initializer_list<uint32_t> List){
            const uint32_t* Node=List.begin();
            this->LayerCount=Layer;
            this->InputCount=Input;
            this->OutputCount=Output;
            this->NodeCount=(uint32_t*)malloc(this->LayerCount*sizeof(uint32_t));
            this->Network=(NerveUnit***)malloc((this->LayerCount+1)*sizeof(NerveUnit**));
            for(register uint32_t i=0;i<this->LayerCount;i++){
                this->NodeCount[i]=Node[i];
                this->Network[i]=(NerveUnit**)malloc(this->NodeCount[i]*sizeof(NerveUnit*));
                for(register uint32_t j=0;j<this->NodeCount[i];j++)
                    this->Network[i][j]=new NerveUnit(i==0?this->InputCount:this->NodeCount[i-1]);
            }
            this->Network[LayerCount]=(NerveUnit**)malloc(this->OutputCount*sizeof(NerveUnit*));
            for(register uint32_t i=0;i<this->OutputCount;i++)
                this->Network[this->LayerCount][i]=new NerveUnit(this->NodeCount[this->LayerCount-1]);
        }
        void FreeMemory(){
            for(register uint32_t i=0;i<LayerCount;i++)
                for(register uint32_t j=0;j<NodeCount[i];j++)
                    delete Network[i][j];
            for(register uint32_t i=0;i<OutputCount;i++)
                delete Network[LayerCount][i];
            for(register uint32_t i=0;i<=LayerCount;i++)
                free(Network[i]);
            free(Network);
            free(NodeCount);
            return;
        }
        ~BasicNeuralNetwork(){
            FreeMemory();
        }
        void Calculate(double* InputValues,double* ReturnValues){
            double* Input;
            double* Output;
            Input=(double*)malloc(InputCount*sizeof(double));
            for(register uint32_t i=0;i<InputCount;i++)
                Input[i]=InputValues[i];
            for(register uint32_t i=0;i<LayerCount;i++){
                Output=(double*)malloc(NodeCount[i]*sizeof(double));
                for(register uint32_t j=0;j<NodeCount[i];j++){
                    Output[j]=0.0;
                    for(register uint32_t k=0;k<Network[i][j]->Count;k++)
                        Output[j]+=Network[i][j]->Params[k]*Input[k];
                    Output[j]+=Network[i][j]->Offset;
                    Output[j]=Func(Output[j]);
                }
                free(Input);
                Input=Output;
            }
            for(register uint32_t i=0;i<OutputCount;i++){
                ReturnValues[i]=0.0;
                for(register uint32_t j=0;j<Network[LayerCount][i]->Count;j++)
                    ReturnValues[i]+=Network[LayerCount][i]->Params[j]*Input[j];
                ReturnValues[i]+=Network[LayerCount][i]->Offset;
                ReturnValues[i]=Func(ReturnValues[i]);
            }
            free(Input);
            return;
        }
        void RandomChange(double x,double num){
            for(register uint32_t i=0;i<LayerCount;i++)
                for(register uint32_t j=0;j<NodeCount[i];j++)
                    if(Rate(num)){
                        for(register uint32_t k=0;k<Network[i][j]->Count;k++)
                            Network[i][j]->Params[k]+=Random(-x,x);
                        Network[i][j]->Offset+=Random(-x,x);
                    }
            for(register uint32_t i=0;i<OutputCount;i++)
                if(Rate(num)){
                    for(register uint32_t j=0;j<Network[LayerCount][i]->Count;j++)
                        Network[LayerCount][i]->Params[j]+=Random(-x,x);
                    Network[LayerCount][i]->Offset+=Random(-x,x);
                }
            return;
        }
        void RandomChange(double x){
            for(register uint32_t i=0;i<LayerCount;i++)
                for(register uint32_t j=0;j<NodeCount[i];j++){
                	for(register uint32_t k=0;k<Network[i][j]->Count;k++)
                        Network[i][j]->Params[k]+=Random(-x,x);
                    Network[i][j]->Offset+=Random(-x,x);
				}
            for(register uint32_t i=0;i<OutputCount;i++){
            	for(register uint32_t j=0;j<Network[LayerCount][i]->Count;j++)
                    Network[LayerCount][i]->Params[j]+=Random(-x,x);
                Network[LayerCount][i]->Offset+=Random(-x,x);
			}
            return;
        }
        bool AddNode(uint32_t Layer){
            if(Layer>=LayerCount){
            	throw BasicNeuralNetworkAddNodeException;
            	return false;
			}
            NodeCount[Layer]++;
            NerveUnit** NewLayer=(NerveUnit**)malloc(NodeCount[Layer]*sizeof(NerveUnit*));
            for(register uint32_t i=0;i<NodeCount[Layer]-1;i++)
                NewLayer[i]=Network[Layer][i];
            NewLayer[NodeCount[Layer]-1]=new NerveUnit(Layer==0?InputCount:NodeCount[Layer-1]);
            free(Network[Layer]);
            Network[Layer]=NewLayer;
            if(Layer==LayerCount-1){
                for(register uint32_t i=0;i<OutputCount;i++)
                    Network[LayerCount][i]->AddMemory();
            }else{
                for(register uint32_t i=0;i<NodeCount[Layer+1];i++)
                    Network[Layer+1][i]->AddMemory();
            }
            return true;
        }
        bool DeleteNode(uint32_t Layer,uint32_t Position){
            if(Layer>=LayerCount){
            	throw BasicNeuralNetworkDeleteNodeException;
            	return false;
			}
            if(Position>=NodeCount[Layer]){
            	throw BasicNeuralNetworkDeleteNodeException;
            	return false;
			}
            NodeCount[Layer]--;
            NerveUnit** NewLayer=(NerveUnit**)malloc(NodeCount[Layer]*sizeof(NerveUnit*));
            delete Network[Layer][Position];
            for(register uint32_t i=0;i<NodeCount[Layer];i++)
                NewLayer[i]=Network[Layer][i>=Position?i+1:i];
            free(Network[Layer]);
            Network[Layer]=NewLayer;
            if(Layer==LayerCount-1){
                for(register uint32_t i=0;i<OutputCount;i++)
                    Network[LayerCount][i]->DeleteParam(Position);
            }else{
                for(register uint32_t i=0;i<NodeCount[Layer+1];i++)
                    Network[Layer+1][i]->DeleteParam(Position);
            }
            return true;
        }
        BasicNeuralNetwork* Copy(){
            BasicNeuralNetwork* Return=new BasicNeuralNetwork(InputCount,OutputCount,LayerCount,NodeCount);
            for(register uint32_t i=0;i<LayerCount;i++)
                for(register uint32_t j=0;j<NodeCount[i];j++){
                    for(register uint32_t k=0;k<Network[i][j]->Count;k++)
                        Return->Network[i][j]->Params[k]=Network[i][j]->Params[k];
                    Return->Network[i][j]->Offset=Network[i][j]->Offset;
                }
            for(register uint32_t i=0;i<OutputCount;i++){
                for(register uint32_t j=0;j<Network[LayerCount][i]->Count;j++)
                    Return->Network[LayerCount][i]->Params[j]=Network[LayerCount][i]->Params[j];
                Return->Network[LayerCount][i]->Offset=Network[LayerCount][i]->Offset;
            }
            return Return;
        }
        void AddLayer(){
            LayerCount++;
            NerveUnit*** NewNetwork=(NerveUnit***)malloc((LayerCount+1)*sizeof(NerveUnit**));
            for(register uint32_t i=0;i<LayerCount-1;i++)
                NewNetwork[i]=Network[i];
            NewNetwork[LayerCount]=Network[LayerCount-1];
            NewNetwork[LayerCount-1]=(NerveUnit**)malloc(2*sizeof(NerveUnit*));
            free(Network);
            Network=NewNetwork;
            uint32_t* NewNodeCount=(uint32_t*)malloc(LayerCount*sizeof(uint32_t));
            for(register uint32_t i=0;i<LayerCount-1;i++)
                NewNodeCount[i]=NodeCount[i];
            NewNodeCount[LayerCount-1]=2;
            free(NodeCount);
            NodeCount=NewNodeCount;
            Network[LayerCount-1][0]=new NerveUnit(NodeCount[LayerCount-2]);
            Network[LayerCount-1][1]=new NerveUnit(NodeCount[LayerCount-2]);
            for(register uint32_t i=0;i<OutputCount;i++){
                delete Network[LayerCount][i];
                Network[LayerCount][i]=new NerveUnit(2);
            }
        }
        bool DeleteLayer(){
            if(LayerCount==0){
            	throw BasicNeuralNetworkDeleteLayerException;
            	return false;
			}
            LayerCount--;
            NerveUnit*** NewNetwork=(NerveUnit***)malloc((LayerCount+1)*sizeof(NerveUnit**));
            for(register uint32_t i=0;i<NodeCount[LayerCount-1];i++)
                delete Network[LayerCount][i];
            delete Network[LayerCount];
            for(register uint32_t i=0;i<LayerCount;i++)
                NewNetwork[i]=Network[i];
            NewNetwork[LayerCount]=Network[LayerCount+1];
            free(Network);
            Network=NewNetwork;
            uint32_t* NewNodeCount=(uint32_t*)malloc(LayerCount*sizeof(uint32_t));
            for(register uint32_t i=0;i<LayerCount;i++)
                NewNodeCount[i]=NodeCount[i];
            free(NodeCount);
            NodeCount=NewNodeCount;
            for(register uint32_t i=0;i<OutputCount;i++){
                delete Network[LayerCount][i];
                Network[LayerCount][i]=new NerveUnit(LayerCount>0?NodeCount[LayerCount-1]:InputCount);
            }
            return true;
        }
};
double Test(BasicNeuralNetwork& Temp,double* InputArray,double* ExpectedOutput){
    double* Output=(double*)malloc(Temp.OutputCount*sizeof(double));
    double Result=0.0;
    Temp.Calculate(InputArray,Output);
    for(register uint32_t i=0;i<Temp.OutputCount;i++)
        Result+=(Output[i]-ExpectedOutput[i])*(Output[i]-ExpectedOutput[i]);
    free(Output);
    return Result;
}
double Test(BasicNeuralNetwork* Temp,double* InputArray,double* ExpectedOutput){
    double* Output=(double*)malloc(Temp->OutputCount*sizeof(double));
    double Result=0.0;
    Temp->Calculate(InputArray,Output);
    for(register uint32_t i=0;i<Temp->OutputCount;i++)
        Result+=(Output[i]-ExpectedOutput[i])*(Output[i]-ExpectedOutput[i]);
    free(Output);
    return Result;
}
template<uint32_t InputCount,uint32_t OutputCount>
struct Group{
	double input[InputCount];
	double output[OutputCount];
};
template<uint32_t InputCount,uint32_t OutputCount>
double ErrorValue(BasicNeuralNetwork& Temp,Group<InputCount,OutputCount>* Data,uint32_t Length){
	double ReturnValue=0.0;
	for(register uint32_t i=0;i<Length;i++)
		ReturnValue+=Test(Temp,Data[i].input,Data[i].output);
	return ReturnValue;
}
template<uint32_t InputCount,uint32_t OutputCount>
double ErrorValue(BasicNeuralNetwork* Temp,Group<InputCount,OutputCount>* Data,uint32_t Length){
	double ReturnValue=0.0;
	for(register uint32_t i=0;i<Length;i++)
		ReturnValue+=Test(Temp,Data[i].input,Data[i].output);
	return ReturnValue;
}
#ifdef BinaryIO
#undef BinaryIO
#endif
#endif